//
// PageFaultAction.cpp
// Usage: Page fault exception action
// CreateDate: 2009-05-03
// Author: MaJiuyue
//
#include <SCI.h>
#include "PageFaultAction.h"
#include "../schedule/Process.h"

PageFaultAction * PageFaultAction::instance;

void PageFaultAction::operator() (u32 vec_no, u32 err_code, u32 eip, u32 cs, u32 eflags)
{
	// Get page_fault address
	addr_t cr2 = 0;
	__asm__ __volatile__("mov %%cr2, %%eax\t\nmov %%eax, %0":"=m"(cr2));

	Process * proc = GetCurrentProcess();
	Context * context = proc->GetContext();

	// Kernel space
	if(cr2>=0xC0000000)
		HandleKernelSpace(context, cr2, cs, eip);
	// User space
	else
		HandleUserSpace(context, cr2, cs, eip);
}

void PageFaultAction::HandleKernelSpace(Context * context, addr_t cr2, u32 cs, u32 eip)
{
	/*
	  !!!IMPORTANT!!!
	    There are two case caused KernelSpace #PF:
	      1. A KernelSpace address has already mapped by PageTable::SetKPDE(
	         ), but current process's PD have not been updated.
	      2. A really KernelSpace #PF happened caused by kerne bugs
	    In case 1, things can be fixed by sync KPD and UPD. But in case 2, 
	    nothing can be done except panic kernel.
	*/
	
	// First, get cr2's KPDE
	pde_t kpde = PageTable::GetKPDE(cr2>>22);
	
	// If case 2, panic
	if( !(kpde&PG_PRESENT) )
		panic("Real KernelSpace #PF at laddr 0x%x.\nProcess %d 0x%x:0x%x", cr2, GetCurrentProcess()->GetPID(), cs, eip);

	// Now we know it's in case 1, we must sync KPD and UPD
	context->GetPageTable()->SyncKPD();
}

void PageFaultAction::HandleUserSpace(Context * context, addr_t cr2, u32 cs, u32 eip)
{
	// Firstly, check if address in cr2 is valid user space address
	if(!context->ValidAddress(cr2))
		panic("#PF: Invalid user space address 0x%x. \nProcess %d 0x%x:0x%x", cr2, GetCurrentProcess()->GetPID(), cs, eip);

	// Now, cr2 is either in 'Heap' or in 'Stack', #PF means kernel must
	// allocat a page frame for that address.
	Resource * res = ResourceManager.RequestResource(
		ServiceCallInterface::PageFrameAllocator::ResourceName
	);
	addr_t paddr = res->CallSCI(
		ServiceCallInterface::PageFrameAllocator::SCIID_GetPageAddr
	);

	// Then map the paddr to cr2
	context->MapPage(cr2, paddr);
}

